/*
* Copyright (c) 2020 The canvas Authors. All rights reserved.
  Use of this source code is governed by a BSD-style license that can be
  found in the LICENSE file.

* Author:         dzlua - https://gitee.com/dzlua/canvas.git
* Email:          505544956@qq.com
* FileName:       canvas_impl.cc
* Create Time:    2020/01/01 15:13:26
* Description:    /
*/

#include "canvas_impl.h"

#define _USE_MATH_DEFINES
#include <math.h>

namespace cvs {

// Point impl
Point::Point() : x(0.f), y(0.f) {}
Point::Point(dtype x_, dtype y_) : x(x_), y(y_) {}
void Point::reset(dtype x_, dtype y_) { x = x_; y = y_; }

// Size impl
Size::Size() : w(0.f), h(0.f) {}
Size::Size(dtype w_, dtype h_) : w(w_), h(h_) {}
void Size::reset(dtype w_, dtype h_) { w = w_; h = h_; }

// Rect impl
Rect::Rect() : x(0.f), y(0.f), w(0.f), h(0.f) {}
Rect::Rect(dtype x_, dtype y_, dtype w_, dtype h_)
  : x(x_), y(y_), w(w_), h(h_) {}
Rect::Rect(const Point &pt, const Size &sz) : x(pt.x), y(pt.y), w(sz.w), h(sz.h) {}
Rect::Rect(const Size &sz) : x(0.f), y(0.f), w(sz.w), h(sz.h) {}
void Rect::reset(dtype x_, dtype y_, dtype w_, dtype h_) { x = x_; y = y_; w = w_; h = h_; }
dtype Rect::l() const { return x; }
dtype Rect::r() const { return x + w; }
dtype Rect::t() const { return y; }
dtype Rect::b() const { return y + h; }
Point Rect::lt() const { Point pt(x,y); return std::move(pt); }
Point Rect::rt() const { Point pt(x+w,y); return std::move(pt); }
Point Rect::lb() const { Point pt(x,y+h); return std::move(pt); }
Point Rect::rb() const { Point pt(x+w,y+h); return std::move(pt); }
Size Rect::size() const { Size sz(w,h); return std::move(sz); }

// Color impl
Color::Color() : r(0.f), g(0.f), b(0.f), a(1.f) {}
Color::Color(dtype r_, dtype g_, dtype b_, dtype a_)
  : r(r_), g(g_), b(b_), a(a_) {}
void Color::reset(dtype r_, dtype g_, dtype b_, dtype a_) {
  r = r_; g = g_; b = b_; a = a_;
}

// Matrix impl
Matrix::Matrix()
  : a(1.f), b(0.f), c(0.f), d(1.f), e(0.f), f(0.f) {}
Matrix::Matrix(dtype a_, dtype b_, dtype c_, dtype d_, dtype e_, dtype f_)
  : a(a_), b(b_), c(c_), d(d_), e(e_), f(f_) {}
void Matrix::reset(dtype a_, dtype b_, dtype c_, dtype d_, dtype e_, dtype f_) {
  a = a_; b = b_; c = c_; d = d_; e = e_; f = f_;
}
void Matrix::identity() {
  a = 1.f; b = 0.f; c = 0.f;
  d = 1.f; e = 0.f; f = 0.f;
}
void Matrix::multipy(const Matrix &m) {
  a = a * m.a + b * m.c;
  b = a * m.b + b * m.d;
  c = c * m.a + d * m.c;
  d = c * m.b + d * m.d;
  e = a * m.e + c * m.f + e;
  f = b * m.e + d * m.f + f;
}
void Matrix::scale(dtype sx, dtype sy) {
  Matrix m(
    sx, 0.f,
    0.f, sy,
    0.f, 0.f
  );
  multipy(m);
}
void Matrix::translate(dtype dx, dtype dy) {
  Matrix m(
    1.f, 0.f,
    0.f, 1.f,
    dx, dy
  );
  multipy(m);
}
void Matrix::rotate(dtype angle) {
  Matrix m(
    ::cos(a), ::sin(a),
    -::sin(a), ::cos(a),
    0.f, 0.f
  );
  multipy(m);
}
void Matrix::transform(const Point& in, Point *out) {
  if (!out) return;

  out->x = a * in.x + c * in.y + e;
  out->y = b * in.x + d * in.y + f;
}

// Base impl
Base::Base() : ref_count_(1) {}
Base::~Base() {}
ulong Base::decref() {
  if (--ref_count_ == 0)
    delete this;
  return ref_count_;
}
ulong Base::incref() { return ++ref_count_; }

// Factory impl
Factory::Factory() {}
Factory::~Factory() {  unload_all_render(); }
Result Factory::load_render(const char *name) {
  if (!name || !*name) return CVS_E_InvalidParam;

  if (is_render_loaded(name))
    return CVS_S_RenderAlreadyExist;

  IDynamicLibrary *dl = IDynamicLibrary::new_instance();
  if (!dl) return CVS_E_OutOfMemory;
  
  if (!dl->open(name)) {
    dl->release();
    return CVS_E_RenderNotExist;
  }
  
  typedef C2DorC3D (*funC2Dor3D)();
  typedef bool (*funSupported)();
  funC2Dor3D c2dOrc3d = (funC2Dor3D)dl->proc("c2d_or_c3d");
  funSupported supported = (funSupported)dl->proc("supported");
  if (!c2dOrc3d || !supported) {
    dl->release();
    return CVS_E_RenderNotRender;
  }

  if (!supported()) {
    dl->release();
    return CVS_E_RenderNotSupported;
  }

  dlRender dlr;
  dlr.dl = dl;
  dlr.name = name;
  dlr.c2d3d = c2dOrc3d();
  dlr.c2d = c2d::RenderType::Default;
  dlr.c3d = c3d::RenderType::Default;
  dlr.create2dRender = 0;
  dlr.create3dRender = 0;

  if (dlr.c2d3d == C2DorC3D::C2D) {
    typedef c2d::RenderType (*funRenderType)();
    typedef Result (*funCreateRender)(IFactory *factory, c2d::IRender **render);

    funRenderType renderType = (funRenderType)dl->proc("render_type");
    funCreateRender createRender = (funCreateRender)dl->proc("create_render");
    if (!renderType || !createRender) {
      dl->release();
      return CVS_E_RenderNotRender;
    }

    dlr.c2d = renderType();
    dlr.create2dRender = createRender;
  } else if (dlr.c2d3d == C2DorC3D::C3D) {
    typedef c3d::RenderType (*funRenderType)();
    typedef Result (*funCreateRender)(IFactory *factory, c3d::IRender **render);

    funRenderType renderType = (funRenderType)dl->proc("render_type");
    funCreateRender createRender = (funCreateRender)dl->proc("create_render");
    if (!renderType || !createRender) {
      dl->release();
      return CVS_E_RenderNotRender;
    }

    dlr.c3d = renderType();
    dlr.create3dRender = createRender;
  } else {
    dl->release();
    return CVS_E_RenderNotRender;
  }

  renders_.push_back(std::move(dlr));

  return CVS_S_Ok;
}
Result Factory::unload_render(const char *name) {
  if (!name || !*name) return CVS_E_InvalidParam;

  for (auto it = renders_.begin(); it != renders_.end(); ++it) {
    if (it->name == name) {
      if (it->dl) it->dl->release();
      renders_.erase(it);
      return CVS_S_Ok;
    }
  }

  return CVS_S_RenderNotExist;
}
Result Factory::unload_all_render() {
  for (auto &dlr : renders_) {
    if (dlr.dl) dlr.dl->release();
  }
  renders_.clear();
  return CVS_S_Ok;
}
bool Factory::is_render_loaded(const char *name) const {
  if (!name || !*name) return false;
  for (auto &dlr : renders_) {
    if (dlr.name == name) return true;
  }
  return false;
}
Result Factory::create_2d_render(c2d::IRender **render, c2d::RenderType type) {
  if (!render) return CVS_E_InvalidParam;

  if (type == c2d::RenderType::Default)
    return create_default_2d_render(render);

  for (auto &dlr : renders_) {
    if (dlr.c2d3d == C2DorC3D::C2D
          && dlr.c2d == type
          && dlr.create2dRender
          && dlr.dl) {
      return dlr.create2dRender(this, render);
    }
  }

  return CVS_E_RenderNotExist;
}
Result Factory::create_3d_render(c3d::IRender **render, c3d::RenderType type) {
  if (!render) return CVS_E_InvalidParam;

  if (type == c3d::RenderType::Default)
    return create_default_3d_render(render);

  typedef Result (*funCreateRender)(c3d::IRender **render);

  for (auto &dlr : renders_) {
    if (dlr.c2d3d == C2DorC3D::C3D
          && dlr.c3d == type
          && dlr.create3dRender
          && dlr.dl) {
      return dlr.create3dRender(this, render);
    }
  }

  return CVS_E_RenderNotExist;
}
Result Factory::create_default_2d_render(c2d::IRender **render) {
  static c2d::RenderType types[] = {
    c2d::RenderType::D2D,
    c2d::RenderType::Gdip,
    c2d::RenderType::SDL
  };

  Result rst = CVS_S_Ok;
  for (uint i = 0; i < CVS_ArraySize(types); ++i) {
    rst = create_2d_render(render, types[i]);
    if (Succeeded(rst)) return rst;
  }

  return CVS_E_RenderNotExist;
}
Result Factory::create_default_3d_render(c3d::IRender **render) {
  static c3d::RenderType types[] = {
    c3d::RenderType::DirectX,
    c3d::RenderType::OpenGL,
  };

  Result rst = CVS_S_Ok;
  for (uint i = 0; i < CVS_ArraySize(types); ++i) {
    rst = create_3d_render(render, types[i]);
    if (Succeeded(rst)) return rst;
  }

  return CVS_E_RenderNotExist;
}

// Factory creater
Result create_factory(IFactory **factory) {
  if (!factory) return CVS_E_InvalidParam;
  *factory = new Factory;
  if (!*factory) return CVS_E_OutOfMemory;
  return CVS_S_Ok;
}

} // end namespace cvs
